import React, {
  useCallback,
  useEffect,
  useImperativeHandle,
  useRef,
  useState,
} from "react";
import { PipelineProps } from "./index";
import type { BoundingBox, Line, LinkLine, Node } from "./types";

interface Path {
  d: string;
}

export interface SvgProps {
  className: string;
  containerRef: React.MutableRefObject<HTMLElement>;
  lines: Line[];
  lineType: PipelineProps["lineType"];
  direction: PipelineProps["direction"];
  setLineColor: (line: Line) => string;
  formOffset?: {
    x?: number;
    y?: number;
  };
  toOffset?: {
    x?: number;
    y?: number;
  };
  linePathRender: (
    dom: React.ReactElement,
    line: Line,
    d: string
  ) => React.ReactElement;
}

const AnimatedSvgPath = (props: {
  linePathRender: SvgProps["linePathRender"];
  d: string;
  line: Line;
  strokeColor: string;
}) => {
  const { strokeColor, line, linePathRender, d } = props;
  const pathRef = useRef<SVGPathElement>(null);
  useEffect(() => {
    if (pathRef.current && pathRef.current.getTotalLength) {
      const pathLength = pathRef.current.getTotalLength();
      pathRef.current.style.strokeDasharray = `${pathLength}px ${pathLength}px`;
      pathRef.current.style.strokeDashoffset = `${pathLength}px`;
      pathRef.current.getBoundingClientRect();
      pathRef.current.style.transition = "stroke-dashoffset 0.3s ease-in-out";
      pathRef.current.style.strokeDashoffset = "0px";
    }
  }, [d]);
  const defaultDom = (
    <path
      d={d}
      style={{
        transition: "stroke 0.3s cubic-bezier(0.645, 0.045, 0.355, 1)",
      }}
      ref={pathRef}
      fill="transparent"
      stroke={strokeColor}
      strokeWidth={2}
    />
  );
  if (linePathRender) {
    return linePathRender(defaultDom, line, d);
  }
  return defaultDom;
};

export const Svg = React.forwardRef<{ draw: () => void }, SvgProps>(
  (props, ref) => {
    const {
      className,
      containerRef,
      lines,
      setLineColor,
      formOffset,
      toOffset,
      linePathRender,
      lineType,
      direction,
    } = props;
    const [paths, setPaths] = useState<Path[]>([]);
    const [svgWidth, setSvgWidth] = useState<number>();

    const draw = useCallback(() => {
      if (!containerRef.current || lines.length < 1) {
        return;
      }
      const result = createSvgPaths(containerRef.current, lines, {
        lineType,
        direction,
        formOffset,
        toOffset,
      });
      setPaths(result);
      setSvgWidth(containerRef.current.scrollWidth);
    }, [containerRef, lines, lineType]);

    useImperativeHandle(ref, () => ({ draw }));

    return (
      <div className={className}>
        <svg xmlns="http://www.w3.org/2000/svg" style={{ width: svgWidth }}>
          {paths.map((path, index) => {
            const strokeColor = setLineColor
              ? setLineColor(lines[index])
              : "#D9D9D9";
            return (
              <AnimatedSvgPath
                key={path.d}
                strokeColor={strokeColor}
                line={lines[index]}
                linePathRender={linePathRender}
                d={path.d}
              />
            );
          })}
        </svg>
      </div>
    );
  }
);

const drawLineMap = {
  straight: createStraightPath,
  curve: createCurvePath,
  rounded: createRoundedPath,
};

/**
 * 创建 SVG Path 连线
 * @param container
 * @param lines
 * @returns
 */
function createSvgPaths(
  container: HTMLElement,
  lines: Line[],
  props: {
    lineType: SvgProps["lineType"];
    direction: SvgProps["direction"];
    formOffset: SvgProps["formOffset"];
    toOffset: SvgProps["toOffset"];
  }
): Path[] {
  const linkLines: LinkLine[] = [];
  const {
    lineType = "straight",
    direction = "horizontal",
    formOffset = {},
    toOffset = {},
  } = props;

  // 第一个起始节点的位置，后面计算会用到
  let firstFromElBox: BoundingBox;

  for (let index = 0; index < lines.length; index += 1) {
    const line = lines[index];

    // DOM 节点查询
    const fromEl = queryElement(container, line.from);
    const toEl = queryElement(container, line.to);

    if (!fromEl || !toEl) {
      // eslint-disable-next-line no-continue
      continue;
    }

    // 节点尺寸位置计算
    const fromElBox = getBoundingBox(fromEl.dom, container);
    const toElBox = getBoundingBox(toEl.dom, container);

    // 保存第一个起始节点的位置
    if (index === 0) {
      firstFromElBox = fromElBox;
    }

    // 构造连线，如果 join 为 true 则分成两根线，先连接到阶段之间的中点，再从中点连往目标节点
    if (line.to.join) {
      if (direction === "horizontal") {
        const formBoxOffset = {
          x:
            formOffset.x ??
            (toElBox.left - fromElBox.left - fromElBox.width) / 2,
          y: formOffset.y ?? firstFromElBox.height / 2,
        };
        const toBoxOffset = {
          x: toOffset.x ?? 0,
          y: toOffset.y ?? toElBox.height / 2,
        };
        const middle = {
          x: toElBox.left - formBoxOffset.x,
          y: firstFromElBox.top + formBoxOffset.y,
        };
        // 汇总连线
        linkLines.push({
          from: {
            x: fromElBox.left + fromElBox.width,
            y: fromElBox.top + fromElBox.height / 2,
            width: fromElBox.width,
            height: fromElBox.height,
          },
          to: {
            x: middle.x,
            y: middle.y,
            crossesWidth: toEl.crossesWidth,
            width: toElBox.width,
            height: toElBox.height,
          },
        });
        linkLines.push({
          from: {
            x: middle.x,
            y: middle.y,
            width: fromElBox.width,
            height: fromElBox.height,
          },
          to: {
            x: toElBox.left + toBoxOffset.x,
            y: toElBox.top + toBoxOffset.y,
            crossesWidth: toEl.crossesWidth,
            width: toElBox.width,
            height: toElBox.height,
          },
        });
      }
      if (direction === "vertical") {
        const formBoxOffset = {
          x: formOffset.x ?? -24,
          y: formOffset.y ?? firstFromElBox.height / 2,
        };
        const toBoxOffset = {
          x: toOffset.x ?? 0,
          y: toOffset.y ?? toElBox.height / 4,
        };
        const middle = {
          x: toElBox.left + formBoxOffset.x,
          y: firstFromElBox.top + formBoxOffset.y,
        };
        // 汇总连线
        linkLines.push({
          from: {
            x: fromElBox.left,
            y: fromElBox.top + fromElBox.height / 2,
            width: fromElBox.width,
            height: fromElBox.height,
          },
          to: {
            x: middle.x,
            y: middle.y,
            crossesWidth: toEl.crossesWidth,
            width: toElBox.width,
            height: toElBox.height,
          },
        });
        linkLines.push({
          from: {
            x: middle.x,
            y: middle.y,
            width: fromElBox.width,
            height: fromElBox.height,
          },
          to: {
            x: toElBox.left + toBoxOffset.x,
            y: toElBox.top + toBoxOffset.y,
            crossesWidth: toEl.crossesWidth,
            width: toElBox.width,
            height: toElBox.height,
          },
        });
      }
    } else {
      if (direction === "horizontal") {
        const formBoxOffset = {
          x: formOffset.x ?? fromElBox.width,
          y: formOffset.y ?? fromElBox.height / 2,
        };
        const toBoxOffset = {
          x: toOffset.x ?? 0,
          y: toOffset.y ?? fromElBox.height / 2,
        };
        // 直接连接两个节点
        linkLines.push({
          from: {
            x: fromElBox.left + formBoxOffset.x,
            y: fromElBox.top + formBoxOffset.y,
            width: fromElBox.width,
            height: fromElBox.height,
          },
          to: {
            x: toElBox.left + toBoxOffset.x,
            y: toElBox.top + toBoxOffset.y,
            crossesWidth: toEl.crossesWidth,
            width: fromElBox.width,
            height: toElBox.height,
          },
        });
      }
      if (direction === "vertical") {
        const formBoxOffset = {
          x: formOffset.x ?? 0,
          y: formOffset.y ?? fromElBox.height / 4,
        };
        const toBoxOffset = {
          x: toOffset.x ?? 0,
          y: toOffset.y ?? toElBox.height / 4,
        };
        linkLines.push({
          from: {
            x: fromElBox.left + formBoxOffset.x,
            y: fromElBox.top + formBoxOffset.y,
            width: fromElBox.width,
            height: fromElBox.height,
          },
          to: {
            x: toElBox.left + toBoxOffset.x,
            y: toElBox.top + toBoxOffset.y,
            crossesWidth: toEl.crossesWidth,
            width: fromElBox.width,
            height: toElBox.height,
          },
        });
      }
    }
  }

  return linkLines.map((item) => {
    if (lineType === "rounded" && direction === "vertical") {
      return {
        d: drawLineMap.straight(item, direction),
      };
    }
    return {
      d: drawLineMap[lineType](item, direction),
    };
  });
}

/**
 * 在指定容器内查询目标
 * @param container
 * @param target
 * @returns
 */
function queryElement(
  container: HTMLElement,
  target: Node
): {
  dom: HTMLElement;
  crossesNode: boolean;
  crossesWidth: number;
} | null {
  const { stageName, taskName } = target;
  const stage = container.querySelector(`[data-stage-name="${stageName}"]`);
  // stage 一定是非空的
  const task = stage!.querySelector(`[data-task-name="${taskName}"]`);
  if (!task) {
    const crossesNode = container!.querySelector(
      `[data-task-name="${taskName}"]`
    ) as HTMLElement;
    if (!crossesNode) return null;
    const parentStage = container.querySelector(
      `[data-stage-name="${crossesNode.dataset.stageName}"]`
    );

    const crossesWidth =
      parentStage!.getBoundingClientRect().left -
      stage.getBoundingClientRect().left;

    return {
      dom: crossesNode as HTMLElement,
      crossesNode: true,
      crossesWidth,
    };
  }
  return {
    dom: task as HTMLElement,
    crossesNode: false,
    crossesWidth: 0,
  };
}

/**
 * 获取目标 HTML 节点的 bounding box
 * @param element 目标节点
 * @param container 容器节点
 * @returns BoundingBox
 */
function getBoundingBox(
  element: HTMLElement,
  container?: HTMLElement
): BoundingBox {
  const nodeBox = element.getBoundingClientRect();
  const containerBox = container?.getBoundingClientRect();
  return {
    left: nodeBox.left - (containerBox?.left || 0),
    top: nodeBox.top - (containerBox?.top || 0) + (container.scrollTop || 0),
    width: nodeBox.width,
    height: nodeBox.height,
  };
}
/**
 * 生成连线的三次贝塞尔曲线
 * https://developer.mozilla.org/zh-CN/docs/Web/SVG/Tutorial/Paths
 * @param linkLine
 * @returns string
 */
function createCurvePath(
  linkLine: LinkLine,
  direction: "horizontal" | "vertical" = "horizontal"
) {
  const { from, to } = linkLine;
  const height = Math.abs(from.y - to.y);

  if (direction === "vertical") {
    const Qs = [
      [from.x - 48 / 3, from.y],
      [from.x - 48 / 2, to.y + (height / 2) * (to.y > from.y ? -1 : 1)],
    ];

    return [
      `M ${from.x} ${from.y}`,
      `Q ${Qs.map((Q) => Q.join(" ")).join(", ")}`,
      `T ${to.x} ${to.y}`,
    ].join(" ");
  }

  const width = Math.abs(Math.abs(from.x - to.x) - to.crossesWidth);

  const Qs = [
    [from.x + to.crossesWidth + width / 4, from.y],
    [
      from.x + to.crossesWidth + width / 3,
      to.y + (height / 2) * (to.y > from.y ? -1 : 1),
    ],
  ];

  return [
    `M ${from.x} ${from.y}`,
    `Q ${Qs.map((Q) => Q.join(" ")).join(", ")}`,
    `T ${to.x} ${to.y}`,
  ].join(" ");
}

function createStraightPath(
  linkLine: LinkLine,
  direction: "horizontal" | "vertical" = "horizontal"
) {
  const { from, to } = linkLine;
  // 差距小余 2，说明是个直线链接，以 from 为准，这样画的比较直
  let y = from.y;
  let y1 = to.y;
  if (Math.abs(y - y1) < Math.max(to.height / 2, 10)) {
    y1 = y;
  }

  if (direction === "vertical") {
    const Qs = [
      [from.x - 24, y],
      [from.x - 24, y1],
    ];
    return [
      `M ${from.x} ${y}`,
      `L ${Qs.map((Q) => Q.join(" ")).join(", ")}`,
      `L ${to.x} ${y1}`,
    ].join(" ");
  }

  const width = Math.abs(Math.abs(from.x - to.x) - to.crossesWidth);

  const Qs = [
    [from.x + width / 2, y],
    [from.x + width / 2, y1],
  ];

  return [
    `M ${from.x} ${y}`,
    `L ${Qs.map((Q) => Q.join(" ")).join(", ")}`,
    `L ${to.x} ${y1}`,
  ].join(" ");
}

function createRoundedPath(linkLine: LinkLine) {
  const { from, to } = linkLine;
  const width = Math.abs(Math.abs(from.x - to.x) - to.crossesWidth);
  const height = from.y - to.y;
  // 差距小余 2，说明是个直线链接，以 from 为准，这样画的比较直
  let y = from.y;
  let y1 = to.y;
  if (Math.abs(y - y1) < Math.max(to.height / 2, 10)) {
    y1 = y;
  }

  if (y1 === y) {
    const Qs = [
      [from.x + to.crossesWidth + width / 2, y],
      [from.x + to.crossesWidth + width / 2, y1],
    ];
    return [
      `M ${from.x} ${y}`,
      `L ${Qs.map((Q) => Q.join(" ")).join(", ")}`,
      `L ${to.x} ${y1}`,
    ].join(" ");
  }

  const middlePointList = [
    [from.x + to.crossesWidth + width / 2, y],
    [from.x + to.crossesWidth + width / 2, y1],
  ];

  const Qs = middlePointList
    .map((middlePoint, index) => {
      const radius = 10;
      const [x, y] = middlePoint;
      // height < 0 说明是向上的, 否则是向下的.计算规则是不同的
      if (index === 0) {
        // height< 0 (x-10,y) (x,y) (x,y+10)
        // height> 0 (x-10,y) (x,y) (x,y-10)
        return [
          [x - radius, y],
          [x, y],
          height < 0 ? [x, y + radius] : [x, y - radius],
        ];
      }
      // height< 0 (x,y-10) (x,y) (x+10,y)
      // height> 0 (x,y+10) (x,y) (x+10,y)
      return [
        height < 0 ? [x, y - radius] : [x, y + radius],
        [x, y],
        [x + radius, y],
      ];
    })
    .flat(1);

  return [
    `M ${from.x} ${y}`,
    `L ${Qs[0]}`,
    `C ${Qs[0]} ${Qs[1]} ${Qs[2]}`,
    `M ${Qs[2]}`,
    `L ${Qs[3]}`,
    `C ${Qs[3]} ${Qs[4]} ${Qs[5]}`,
    `T ${to.x} ${y1}`,
  ].join(" ");
}
